<?php
class node_Model_NodeService 
{
	// mService->insert returns ('success', 'last-insert-id', 'errorMessage');
	public function __construct()
	{
	   	//require_once (APP_PATH. "/modules/node/Model/Node.php");
		//require_once (APP_PATH. "/modules/node/Model/NodeType.php");
		$this->mNode = new node_Model_Node();
		$this->mNodeType = new node_Model_NodeType();
		$this->db = Zend_Db_Table::getDefaultAdapter();
	}
	/*
	 * @param: $data: the request Data
	 * @param: $nodeTypeInfo: To determine what to insert to the table
	 * 		because each nodeType has different fields
	 */
	public function insertNewNode($data, $nodeTypeInfo)
	{
		
		$tmp = $this->prepareDataToInsert($data, $nodeTypeInfo);
		if (!$tmp['success'])
			return $tmp;
		$dataToInsert = $tmp['dataToInsert'];
		try
		{
			$ret['lastInsertId'] = $this->mNode->insert($dataToInsert);
			// TODO : update `articles` tables if needed
			if ($nodeTypeInfo['needs_translation'])
			{
				//insert an article
				//update `article_id` in $nodes table
				//require_once (APP_PATH . '/modules/article/Model/Article.php');
				$mArticle = new article_Model_Service();
				
				$t['article_title'] = $data['title'];
				$t['article_language'] = get_language_from_int($data['language']);
				$t['article_body'] = $data['body'];
				$t['article_module'] = $nodeTypeInfo['type'];
				$t['article_status']  = get_status_from_int($data['status']);
				if ($nodeTypeInfo['has_keywords'])
					$t['article_keywords'] = $data['keywords'];				 
                if (!$article_id = $mArticle->insertRaw($t,false)) //no transaction
                {
                	throw new Exception(t(503));
                }
				// update the node db
				$sql = sprintf("UPDATE `" . $nodeTypeInfo['node_table_name'] . "` set `article_id`='%d' WHERE `id`='%d'", $article_id, $ret['lastInsertId']);
				$this->db->query($sql);
			}				
			$ret['success'] = true;
    		return $ret;
		}
		catch (Exception $e)
		{
			die_a($e->getMessage());
			$ret['success'] = false;
			$ret['errorMessage'] = $e->getMessage();
			return $ret;
		}
	}

	/**
	 * 
	 */
	public function updateNode($data, $nodeTypeInfo, $node)
	{
		$dataToUpdate = $this->prepareDataToUpdate($data, $nodeTypeInfo, $node);
		try
		{
			foreach ($dataToUpdate as $key=>$val)
				$node[$key] = $val; 
			$node->save();
			//TODO : update `articles` tables if needed
			$ret['success'] = true;
    		return $ret;
		}
		catch (Exception $e)
		{
			$ret['success'] = false;
			$ret['errorMessage'] = $e->getMessage();
			return $ret;
		}
	}
	
	
	/*
	 * This function is a chance for other node submodules to inject their prepareDataToInsert()
	 * @param: $data: request data
	 */
	public function prepareDataToInsert($data, $nodeTypeInfo)
	{
		// TODO in version 3
		 // prepare data based on db_fields && $data
		// foreach $nodeTypeInfo['db_fields'] as $f...
		//
		// For now, just check every fields

		$fields = array();
		$metadata = array();
		//title
		if ($nodeTypeInfo['has_title'])
			$fields[] = 'title';
			
		//body
		if ($nodeTypeInfo['has_body'])
			$fields[] = 'body';
			
		//language
		if ($nodeTypeInfo['needs_translation'])
			$fields[] = 'language';

		//category
		if ($nodeTypeInfo['has_category'])
		{
			$fields[] = 'category';
	    	//require_once (APP_PATH . '/modules/category/Model/Category.php');
	    	$mCategory = new category_Model_Category();
			$mCategory->setTables($nodeTypeInfo['category_table_name']);
			$tmp = $mCategory->findByPrimary($data['category']);
			$metadata['category_name'] = $tmp['name'];
		}	
		//status
		$fields[] = 'status';
		if (!isset($data['status']))
			$data['status'] = 0; //default : 'published'
		
		//keywords
		if ($nodeTypeInfo['has_keywords'])
			$fields[] = 'keywords';

		$dataToInsert = clu_array_merge($fields, $data);
				
		//poster_id
       	$auth = Zend_Auth::getInstance();
       	$uIdentity = $auth->getIdentity();
       	$dataToInsert['poster_id'] = $uIdentity->id; //$data['poster_id'];
       	//created
		$dataToInsert['created'] = time();
		//category_tree_path
		if ($nodeTypeInfo['has_category'])
		{
			$dataToInsert['category_tree_path'] = $tmp['tree_path'];
		}		
		//poster_name
		$metadata['poster_name'] = $uIdentity->name;
		// if using the sam
		//if ($nodeTypeInfo['node_table_name'] == "nodes")
		$dataToInsert['type'] = $nodeTypeInfo['type'];
		// TODO Hook the stuff here to the $dataToInsert()
		// pmPrepareDataToInsert
		$classMethods = get_class_methods($this);
		$insertHook = $nodeTypeInfo['type'] . "PrepareDataToInsert"; 
		if (in_array( $insertHook, $classMethods))
		{
			$tmp = $this->{$insertHook}($data, $nodeTypeInfo);
			if (!$tmp['success'])
			{
				return $tmp;
			}
			$additionalDataToInsert = $tmp['additionalData']; 
			if (isset($additionalDataToInsert['metadata']))
				$metadata = array_merge($metadata, $additionalDataToInsert['metadata']);
			$dataToInsert = array_merge($dataToInsert,$additionalDataToInsert );
		}
		$dataToInsert['metadata'] = serialize($metadata);
		$retWrapper['success'] = true;
		$retWrapper['dataToInsert'] = $dataToInsert;
		return $retWrapper;
	}
	
	/*
	 * This function is called as an additional hook for function @prepareDataToInsert()
	 * It will prepare additional data like : `receiver_id`, `receiver_name`
	 * $metadata will be returned in array, not yet serialized
	 * 	$ret = 
	 * 		('receiver_id' => 1,
	 * 		 'metadata' => array('receiver_name' => "abc", ...)
	 * 		)
	 * 	
	 */
	public function pmPrepareDataToInsert($data, $nodeTypeInfo)
	{
		$additionalData = array();
		if (isset($data['receiver_id']))
			$additionalData['receiver_id'] = $data['receiver_id'];
		//whether or not this pm is a reply
		if (strpos(trim($data['title']), "Re: ") === 0)
		{
			$additionalData['title_is_reply'] = 1;
			//trim the title here$
			$additionalData['title'] = trim(substr($data['title'], 4, -1));
		}
		else
			$additionalData['title_is_reply'] = 0;
		
		$metadata = array();
		// get receiver_name to store to metadata
		//require_once (APP_PATH . '/modules/user/Model/User.php');
		$mUser = new user_Model_User();
		if (!$receiver = $mUser->findByPrimary($data['receiver_id']))
		{
			$ret['success'] = false;
			$ret['errorMessage'] = "Could not find receiver id";
			return $ret;
		}	
		else
		{
			$metadata['receiver_name'] = $receiver->name;
		}
		//todo: get poster_category & receiver_category here
		$additionalData['metadata'] = $metadata;
		$ret['success'] = true;
		$ret['additionalData'] = $additionalData;
		return $ret;
	}
	
	/**
	 * TODO: merge 2 functions prepareAtatoUpdate and prepareDataToInsert
	 * 	into 1 function, taht would be better
	 */

	public function prepareDataToUpdate($data, $nodeTypeInfo, $node)
	{
		
		$fields = array();
		$metadata = unserialize($node['metadata']);
		
		//title
		if ($nodeTypeInfo['has_title'])
		{
			$dataToUpdate['title'] = $data['title'];
		}	
		//body
		if ($nodeTypeInfo['has_body'])
			$dataToUpdate['body'] = $data['body'];
			
		//language
		if ($nodeTypeInfo['needs_translation'])
			$dataToUpdate['language'] = $data['language'];

		//category
		if ($nodeTypeInfo['has_category'] && $data['category'] != $node['category'])
		{
	    	//require_once (APP_PATH . '/modules/category/Model/Category.php');
	    	$mCategory = new category_Model_Category();
			$mCategory->setTables($nodeTypeInfo['category_table_name']);
			$tmp = $mCategory->findByPrimary($data['category']);
			$metadata['category_name'] = $tmp['name'];
			$dataToUpdate['category'] = $data['category'];
			$dataToUpdate['category_tree_path'] = $tmp['tree_path'];
		}	
		//status
		if (isset($data['status']))
			$dataToUpdate['status'] = $data['status'];
		else
			$dataToUpdate['status'] = $node['status']; //default : 'published'

		//keywords
		if ($nodeTypeInfo['has_keywords'])
			$dataToUpdate['keywords'] = $data['keywords'];

//		$dataToInsert = clu_array_merge($fields, $data);
				
		//poster_id
       	$auth = Zend_Auth::getInstance();
       	$uIdentity = $auth->getIdentity();
       	$dataToUpdate['updater_id'] = $uIdentity->id;
       	//created
		$dataToUpdate['updated'] = time();
		//poster_name
		$metadata['updater_name'] = $uIdentity->name;
		$dataToUpdate['metadata'] = serialize($metadata);
		
		return $dataToUpdate;
	}
	/**
	 * Because `node_types` table only has 2 fields
	 * 'type' & 'metadata', we need to unserialize the 'metadata' here
	 * 
	 */
	public function getNodeTypeInfo($nodeType)
	{
		$nodeTypeInfo = $this->mNodeType->findByPrimary($nodeType);
		if (!$nodeTypeInfo)
			return null;
		$ret = unserialize($nodeTypeInfo['metadata']);
		$ret['type'] = $nodeType;
		return $ret;
	}
	
	public function insertNewNodeType($nodeType)
	{
		try
		{
			$ret['lastInsertId'] = $this->mNodeType->insert($nodeType);
    		$ret['success'] = true;
    		return $ret;
		}
		catch (Exception $e)
		{
			$ret['success'] = false;
			$ret['errorMessage'] = $e->getMessage();
			return $ret;
		}
	}

	public function saveNodeType($nodeType)
	{
		$sql = sprintf("UPDATE `node_types` set `metadata`='%s' WHERE `type`='%s'", $nodeType['metadata'], $nodeType['type']);
		return $this->db->query($sql);
	}
	
	public function getNodeData($nodeTypeInfo, $nodeId, $options = null)
	{
		//get nodeTableName
		//select * from nodeTableName
		$this->mNode->setNodeTableName($nodeTypeInfo['node_table_name']);
		try {
			$ret['node'] = $this->mNode->findByPrimary($nodeId);
		}
		catch (Exception $e)
		{
			$ret['success'] = false;
			$ret['node'] = null;
			$ret['errorMessage'] = $e->getMessage();		
		}
		
		if ($ret['node'] == null)
		{
//			$ret['success'] = false;
//			$ret['errorMessage'] = "Could not find";
			return $ret;
		}
		else
		{

			$ret['success'] = true;
			//get extra data
			//TODO: $ret['node']->toArray() here?
			$ret['node'] = $ret['node']->toArray();
			//metadata (poster_name...)
			if (isset($ret['node']['metadata']) && strlen($ret['node']['metadata']) > 0)
			{
				$ret['node']['metadata'] = unserialize($ret['node']['metadata']);
			}
			//extra data like $date,..
			if ($nodeTypeInfo['needs_translation'] && isset($options['get_article']))
			{
				$sess = new Zend_Session_Namespace(CLU_NS);
				if ($sess->viewLang != get_language_from_int($ret['node']['language']))
				{
					//get the translation here
					//require_once (APP_PATH . '/modules/article/Model/Article.php');
					$mArticle = new article_Model_Service();
					$article = $mArticle->getArticleData($ret['node']['article_id']);
					$ret['article'] = $article;
				}
			}
				
		}
		return $ret;
	}
	
	public function deleteNode($nodeTypeInfo, $nodeIds)
	{
//		$sql = sprintf("DELETE FROM `" . $nodeTypeInfo['node_table_name'] . "` WHERE `id`='%d'", $nodeId);
		$sql = sprintf("DELETE FROM `" . $nodeTypeInfo['node_table_name'] . "` WHERE id IN (".$nodeIds.")");
		try 
		{ 
			$this->db->query($sql);
			$this->updateDbAfterDelete($nodeTypeInfo, $nodeIds);
			$ret['success'] = true;
//			$ret['id'] = $nodeId;
			return $ret;
		}
		catch (Exception $e)
		{
			$ret['success'] = false;
			$ret['errorMessage'] = $e->getMessage();
			return $ret;
		}
	}
	
	public function updateDbAfterView($nodeTypeInfo, $nodeId)
	{
		if ($nodeTypeInfo['node_table_name'] != "private_messages")
		{
			$sql = sprintf("UPDATE `" . $nodeTypeInfo['node_table_name'] . "` set `views`=`views`+1 WHERE `id`='%d'", $nodeId);
			
		}
		else //pm
			$sql = sprintf("UPDATE `" . $nodeTypeInfo['node_table_name'] . "` set `status`=1 WHERE `id`='%d'", $nodeId); //set to read
		$this->db->query($sql);	
	}
	
	public function updateDbAfterComments($nodeTypeInfo, $nodeId)
	{
		if ($nodeTypeInfo['node_table_name'] != "private_messages")
		{
			$sql = sprintf("UPDATE `" . $nodeTypeInfo['node_table_name'] . "` set `comments`=`comments`+1 WHERE `id`='%d'", $nodeId);
			
		}
		else //pm
			$sql = sprintf("UPDATE `" . $nodeTypeInfo['node_table_name'] . "` set `status`=1 WHERE `id`='%d'", $nodeId); //set to read
		$this->db->query($sql);	
	}
	//TODO 
	public function updateDbAfterDelete($nodeTypeInfo, $nodeId)
	{
		return;
	}
	//TODO: set "not_up_to_date" to user's friends' updates
	public function updateDbAfterInsert($nodeTypeInfo, $nodeId)
	{
		return;
	}
	
	public function findNodeByPrimary($nodeTypeInfo, $id)
	{
		$this->mNode->setNodeTableName($nodeTypeInfo['node_table_name']);
		return $this->mNode->findByPrimary($id);
	}

	/**
	 * This function merges all the previously saved search data & request params
	 * 
	 * @param: $requestData The search params that user submitted
	 * @return: Void Or $currentSearchData ?? TODO
	 */
	public function prepareNodeSearchData($requestData, $nodeTypeInfo)
	{
		
//		$keys = array('poster_id', 'category', 
//					  'type' /*node type*/,  
//					  'status', 'rating',
//					  'items_per_page', 'page'
//        	          );
		
		$ret = array();        	          
		//terms
		if (isset($requestData['terms']) && !empty($requestData['terms']))
			$ret['terms'] = $requestData['terms'];

		//category
		if (isset($requestData['category']))
			$ret['category'] = $requestData['category'];

		//category
		if (isset($requestData['type']))
			$ret['type'] = $requestData['type'];
			
		//status
		if (isset($requestData['status'])) // -1, 0, ..
			$ret['status'] = $requestData['status'];
		else //by default only search the published nodes
			$ret['status'] = 0;// published for normal nodes, 'unread' for private messages
		//poster_name -> poster_id
		if (isset($requestData['poster_name']) && !empty($requestData['poster_name']))
		{
			//require_once (APP_PATH . '/modules/user/Model/User.php');
			$uModel = new user_Model_User();
			if ($u = $uModel->findByExpression("name", $requestData['poster_name'], "STRING", "equals"))
				$ret['poster_id'] = $u['id'];
			else
			{ 
				$ret['success'] = false;
				$ret['errorMessage'] = "No user with that name found";
				return $ret;
			}	
		}
		
		//homed or sticky
		/*
		if (isset($requestData['homed']))
			$ret['items_per_page'] = $requestData['items_per_page'];
		else
			$ret['items_per_page'] = 10;
		*/
		//items_per_page
		if (isset($requestData['items_per_page']))
			$ret['items_per_page'] = $requestData['items_per_page'];
		else
			$ret['items_per_page'] = 10;

		//page number
		if (isset($requestData['page']))
			$ret['page'] = $requestData['page'];
		else
			$ret['page'] = 1;
		//TODO
		//order_by: created_time, or updated_time, views, ratings, number_of_ratings 

		if (isset($requestData['order_by']))
			$ret['order_by'] = $requestData['order_by'];
		else
			$ret['order_by'] = "created";
		
		//asc_or_desc: acsending or descending
		if (isset($requestData['asc_or_desc']))
			$ret['asc_or_desc'] = $requestData['asc_or_desc'];
		else
			$ret['asc_or_desc'] = "asc"; //default Ascending

		//prepareSearchData hook
		$prepareSearchDataHook = $nodeTypeInfo['type'] ."PrepareNodeSearchData";
		if (in_array($prepareSearchDataHook , get_class_methods($this)))
		{
			$additionalSearchData = $this->{$prepareSearchDataHook}($requestData, $nodeTypeInfo);
			if (!$additionalSearchData['success'])
				return $additionalSearchData;
			else
				$ret = array_merge($ret, $additionalSearchData['additionalNodeSearchData']);
		}	
		$retWrapper['success'] = true;
		$retWrapper['nodeSearchData'] = $ret;
		return $retWrapper;
	}
		
	public function pmPrepareNodeSearchData($requestData, $nodeTypeInfo)
	{
		$sess = new Zend_Session_Namespace(CLU_NS);
		if (!$sess->userId  ||
		 (strpos ($sess->userPermissions, 'administer pm') === false 
		 	    && (isset($requestData['receiver_id']) && $requestData['receiver_id'] != $sess->userId
		 	    || (isset($requestData['id']) && $requestData['id'] != $sess->userId
		  )))
		)
		{
			$retWrapper['success'] = false;
			$retWrapper['errorMessage'] = "Not allowed to view this page";
			return $retWrapper;
		}
		// either viewing your own messages or logged in user is admin 
		if (isset($requestData['receiver_id']))
			$ret['receiver_id'] = $requestData['receiver_id'];
		else
			$ret['receiver_id'] = $sess->userId;
		//flag
		if (isset($requestData['flag']))
			$ret['flag'] = $requestData['flag'];
		
		//other stuff here to prepare if any... Currently nothing else
		$retWrapper['success'] = true;
		$retWrapper['additionalNodeSearchData'] = $ret;
		return $retWrapper;
	}

	public function blogPrepareNodeSearchData($requestData, $nodeTypeInfo)
	{
		return array("success" => true, 
					"additionalNodeSearchData" => array());
	}
	/**
	 * Use the $node service to search for articles...
	 */
	public function search($data, $nodeTypeInfo)
	{
//		die_a($data);
		/*
		 * Search criteria , so far, as of 2009.10.15 includes
		 *  'status', 'category', 'poster_id', 'terms' (search terms),
		 *  'items_per_page', 'page'
		 *  'order_by', 'asc_or_desc' 
		 */
		$db = $this->db;
    	$select = new Zend_Db_Select($db);
		$select = $db->select();
        // Add a FROM clause
        $select->from( array('n' => $nodeTypeInfo['node_table_name']));
//        die_a($nodeTypeInfo);
//		die_a($data);
        
        //node Type
		if ($nodeTypeInfo['node_table_name'] == 'nodes')
		{
        	$select->where("n.type = ". $db->quote($nodeTypeInfo['type'],'STRING'));
		}
        //category
    	if(isset($data['category']))
    	{
       	 	$s = '(n.category = ' . $db->quoteinto($data['category'],'INTEGER');
       	 	$s = $s. " OR n.category_tree_path like '%_" . $db->quoteinto($data['category'],'INTEGER') . "_%')";
    		$select->where($s);
    	}
//    	die($data['terms']);
	   if(isset($data['terms']))
    	{
    		   $select->where('n.title LIKE ?',$db->quoteinto("%".$data['terms']."%","STRING"));
    	}
    	//status
    	if (isset($data['status']) && $data['status'] != -1)
    	{
    		$select->where("n.status = " . $db->quoteinto($data['status'],'INTEGER'));
    	}
    	//poster_id
    	if (isset($data['poster_id']))
    	{
    		$select->where("n.poster_id = " . $db->quoteinto($data['poster_id'],'INTEGER'));
    	}
    	
    	//order & asc_or_desc
    	if(isset($data['asc_or_desc']))
			$select->order(array(sprintf('n.%s %s',$data['order_by'], $db->quoteinto($data['asc_or_desc'],'STRING'))));
		
		if(isset($data['views']))
			$select->order('views');
		
		if(isset($data['comments']))
			$select->order('comments');
			
		//search Hook
		$searchHook = $nodeTypeInfo['type'] . "Search"; 
		if (in_array($searchHook, get_class_methods($this)))
			return $this->{$searchHook}($select, $data);
//		die_a($select->__toString());
    	return $select;
	}
	/*
	 * Private Messages Hook for search
	 */
	public function pmSearch($select, $data)
	{
	   	if (isset($data['receiver_id']))
    	{
    		$select->where("n.receiver_id = " . $this->db->quoteinto($data['receiver_id'],'INTEGER'));
    	}
	   	if (isset($data['flag']))
    	{
    		$select->where("n.flag = " . $this->db->quoteinto($data['flag'],'INTEGER'));
    	}
    	//TODO: distinct Titles
    	return $select;
	}
		
	/*
	 * return the list of all available node types
	 */
	public function getNodeTypeList()
	{
		$sql = sprintf("SELECT * FROM `node_types`");
		try 
		{
			$ret['nodeTypes'] = $this->db->query($sql);
			$ret['success'] = true;
			return $ret;
		}
		catch (Exception $e)
		{
			$ret['success'] = false;
			$ret['errorMessage'] = $e->getMessage();
			return $ret;
		}
	}
	
	public function updateStatusNode($nodeTypeInfo, $nodeIds, $status)
	{
		$sql = sprintf("UPDATE `" . $nodeTypeInfo['node_table_name'] . "` SET status = " . $status . " WHERE id IN (".$nodeIds.")");
		try 
		{ 
			$this->db->query($sql);
			$ret['success'] = true;
			return $ret;
		}
		catch (Exception $e)
		{
			$ret['success'] = true;
			$ret['errorMessage'] = $e->getMessage();
			return $ret;
		}
	}
	
	public function updateFlagNode($nodeTypeInfo, $nodeIds, $flag)
	{
		$sql = sprintf("UPDATE `" . $nodeTypeInfo['node_table_name'] . "` SET flag = " . $flag . " WHERE id IN (".$nodeIds.")");
		try 
		{ 
			$this->db->query($sql);
			$ret['success'] = true;
			return $ret;
		}
		catch (Exception $e)
		{
			$ret['success'] = true;
			$ret['errorMessage'] = $e->getMessage();
			return $ret;
		}
	}
	
	public function getData($id, $options)
	{
		if ($node = $this->mNodeType->findByPrimary($id))
		{
			$ret = $node->toArray();
			if ($options['get_article'] == true)
			{
				$article = new article_Model_Service();
				$ret['article'] = $article->getArticleData($node['article_id']);
			}
			return $ret;
		}
		else
			return null;
	}
}
?>